﻿//****************************************************************************************************
//
// Copyright © ProFast Computing 2012-2016
//
//****************************************************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace PFProcessObjects
{
    /// <summary>
    /// Class for running Windows processes.
    /// </summary>
    public class PFProcess
    {
        //private work variables
        private StringBuilder _msg = new StringBuilder();
        Process oProcess = new Process();

        //private variables for properties
        private string _workingDirectory = string.Empty;
        private string _executableToRun = string.Empty;
        private string _arguments = string.Empty;
        private bool _createNoWindow = true;
        private PFProcessWindowStyle _windowStyle = PFProcessWindowStyle.Hidden;
        private bool _useShellExecute = false;
        private bool _redirectStandardOutput = false;
        private bool _redirectStandardError = false;
        private bool _redirectStandardInput = false;
        private string _processMessages = string.Empty;
        private int _processExitCode = -1;
        private TimeSpan _elapsedTime = TimeSpan.MinValue;
        private bool _checkIfProcessWaitingForInput = false;
        private int _maxProcessRunSeconds = 60;

        //constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        public PFProcess()
        {
            ;
        }

        //properties

        /// <summary>
        /// WorkingDirectory for the process.
        /// </summary>
        public string WorkingDirectory
        {
            get
            {
                return _workingDirectory;
            }
            set
            {
                _workingDirectory = value;
            }
        }

        /// <summary>
        /// Path to the executable file to run.
        /// </summary>
        public string ExecutableToRun
        {
            get
            {
                return _executableToRun;
            }
            set
            {
                _executableToRun = value;
            }
        }

        /// <summary>
        /// Command line arguments to be passed to the executable.
        /// </summary>
        public string Arguments
        {
            get
            {
                return _arguments;
            }
            set
            {
                _arguments = value;
            }
        }

        /// <summary>
        /// If true, process will be started in a new window.
        ///  This value should usually be specified as false.
        /// </summary>
        public bool CreateNoWindow
        {
            get
            {
                return _createNoWindow;
            }
            set
            {
                _createNoWindow = value;
            }
        }

        /// <summary>
        /// Specify windows style as Hidden, Maximized, Minimized or Normal.
        /// </summary>
        public PFProcessWindowStyle WindowStyle
        {
            get
            {
                return _windowStyle;
            }
            set
            {
                _windowStyle = value;
            }
        }

        /// <summary>
        /// Should usually be set to false.
        ///  If set to true, caller will not be able to capture any output messages generated by the process.
        /// </summary>
        public bool UseShellExecute
        {
            get
            {
                return _useShellExecute;
            }
            set
            {
                _useShellExecute = value;
            }
        }

        /// <summary>
        /// Set to true so that any non-error messages ouput by the process can be captured.
        ///  UseShellExecute must be set to false in order to capture messages.
        /// </summary>
        public bool RedirectStandardOutput
        {
            get
            {
                return _redirectStandardOutput;
            }
            set
            {
                _redirectStandardOutput = value;
            }
        }

        /// <summary>
        /// Set to true so that process error messages can be captured.
        ///  UseShellExecute must be set to false in order to capture messages.
        /// </summary>
        public bool RedirectStandardError
        {
            get
            {
                return _redirectStandardError;
            }
            set
            {
                _redirectStandardError = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the input for an application is read from the Process.StandardInput stream.
        /// </summary>
        /// <remarks>A Process can read input text from its standard input stream, typically the keyboard. 
        /// By redirecting the StandardInput stream, you can programmatically specify the input of a process. 
        /// For example, instead of using keyboard input, you can provide text from the contents of a designated file or output from another application.</remarks>
        public bool RedirectStandardInput
        {
            get
            {
                return _redirectStandardInput;
            }
            set
            {
                _redirectStandardInput = value;
            }
        }

        /// <summary>
        /// Any messages, including error messages, output by the process while it was running.
        /// </summary>
        public string ProcessMessages
        {
            get
            {
                return _processMessages;
            }
            set
            {
                _processMessages = value;
            }
        }

        /// <summary>
        /// Final exit code reported by the process when it terminates.
        /// </summary>
        public int ProcessExitCode
        {
            get
            {
                return _processExitCode;
            }
            set
            {
                _processExitCode = value;
            }
        }

        /// <summary>
        /// Time process took to complete.
        /// </summary>
        public TimeSpan ElapsedTime
        {
            get
            {
                return _elapsedTime;
            }
            set
            {
                _elapsedTime = value;
            }
        }

        /// <summary>
        /// If true, a check will be done to make sure the process is not stalled waiting for user or some other external input.
        /// </summary>
        public bool CheckIfProcessWaitingForInput
        {
            get
            {
                return _checkIfProcessWaitingForInput;
            }
            set
            {
                _checkIfProcessWaitingForInput = value;
            }
        }

        /// <summary>
        /// Specify the maximum number of seconds process can run before it will be terminated even if it is still running.
        ///  Set this value to 0 to allow process to run as long as possible. If value is less than 1, no attempt will be made to terminate a long running process.
        /// </summary>
        public int MaxProcessRunSeconds
        {
            get
            {
                return _maxProcessRunSeconds;
            }
            set
            {
                _maxProcessRunSeconds = value;
            }
        }



        //methods

        /// <summary>
        /// Method to run process defined by this instance.
        /// </summary>
        public void Run()
        {
            int maxRunMilliseconds = _maxProcessRunSeconds * 1000;
            DateTime startTime = DateTime.MinValue;
            DateTime currentTime = DateTime.MinValue;
            TimeSpan elapsedTime = TimeSpan.MinValue;
            bool processRanTooLong = false;
            bool processStalledWaitingForInput = false;

            oProcess.StartInfo.WorkingDirectory = _workingDirectory;
            oProcess.StartInfo.FileName = _executableToRun;
            oProcess.StartInfo.Arguments = _arguments;
            oProcess.StartInfo.CreateNoWindow = _createNoWindow;
            oProcess.StartInfo.WindowStyle = (ProcessWindowStyle)(_windowStyle);
            oProcess.StartInfo.UseShellExecute = _useShellExecute;
            oProcess.StartInfo.RedirectStandardOutput = _redirectStandardOutput;
            oProcess.StartInfo.RedirectStandardError = _redirectStandardError;
            oProcess.StartInfo.RedirectStandardInput = _redirectStandardInput;

            oProcess.Start();

            startTime = DateTime.Now;

            System.Threading.Thread.Sleep(1000);
            while (oProcess.HasExited == false)
            {
                currentTime = DateTime.Now;
                elapsedTime = currentTime.Subtract(startTime);

                if (_checkIfProcessWaitingForInput)
                {
                    foreach (ProcessThread thread in oProcess.Threads)
                    {
                        if (oProcess.HasExited == false)
                        {
                            if (thread.ThreadState == ThreadState.Wait
                                && thread.WaitReason == ThreadWaitReason.LpcReply)
                            {
                                oProcess.Kill();
                                processStalledWaitingForInput = true;
                            }
                        }
                    }
                }

                if (elapsedTime.TotalMilliseconds > maxRunMilliseconds
                    && maxRunMilliseconds > 0)
                {
                    oProcess.Kill();
                    processRanTooLong = true;
                }

                System.Threading.Thread.Sleep(1000);
            }
            _msg.Length = 0;
            if (processRanTooLong)
            {
                _msg.Append("Process terminated after exceeding maximum allowed runtime. ");
                _msg.Append("Max runtime: ");
                _msg.Append(maxRunMilliseconds.ToString("#,##0 ms"));
                _msg.Append(Environment.NewLine);
            }
            if (processStalledWaitingForInput)
            {
                _msg.Append("Process terminated after stalling while waiting for external input. ");
                _msg.Append(Environment.NewLine);
            }

            if (_redirectStandardOutput)
            {
                _msg.Append(oProcess.StandardOutput.ReadToEnd());
                _msg.Append(" ");
            }
            if (_redirectStandardError)
            {
                _msg.Append(oProcess.StandardError.ReadToEnd());
            }
            _processMessages = _msg.ToString();

            oProcess.WaitForExit();
            _processExitCode = oProcess.ExitCode;

            currentTime = DateTime.Now;
            elapsedTime = currentTime.Subtract(startTime);
            _elapsedTime = elapsedTime;
        
        }

    }//end class
}//end namespace
